#include <map>
#include <vector>
#include <string>
#include <sstream>
#include "meson.hpp"

namespace meson {

  typedef unsigned short Handshake;
  typedef unsigned short Header;
  typedef unsigned long long PeerIdent;

  enum {
    READBUF_SIZE = 4096,
  };

  enum PeerState {
    PS_CONNECTING,
    PS_HEADER,
    PS_BODY,
    PS_ACCEPTED,
  };


  void onRead(bufferevent *evt, void *args);
  void onWrite(bufferevent *evt, void *args);
  void onError(bufferevent *evt, const short what, void *args);

  struct Peer {
    int fd;
    PeerState state;
    bufferevent *evt;
    sockaddr_in addr;
    HookBase *hook;

    Peer(const int fd) {
      evt = bufferevent_new(onRead, onWrite, onError, this);
      if (!evt) {
	throw Error("bufferevent_new");
      }
      this->fd = fd;
      bufferevent_setfd(evt, fd);
      hook = NULL;
    }

    ~Peer() {
      bufferevent_free(evt);
      close(fd);
    }
  };


  typedef std::map<PeerIdent, Peer *> PeerMap;
  typedef std::map<PeerIdent, HookBase *> HookMap;

  sockaddr_in listenAddr;
  int listenFd;
  event listenEvt;

  PeerMap peers;
  HookMap hooks;

  void registerPeer(const PeerIdent ident, const Peer *peer) {
    std::pair<bool, PeerMap::iterator> res = 
      peers.insert(std::make_pair(ident, peer));
    if (!res.first) {
      delete res->second;
      res->second = peer;
    }
  }

  
  PeerIdent makeIdent(const unsigned int ip, const unsigned short port) {
    return PeerIdent(port) << 32 | ip;
  }

  /*
  PeerIdent makeIdent(const std::string &repr) {
    std::istringstream is(repr);
  }
  */

  int setNonblock(int fd) {
    int res = fcntl(fd, GETFL);
    if (res < 0) {
      return -1;
    }
    res = fcntl(fd, SETFL, res | O_NONBLOCK);
    if (res < 0) {
      return -1;
    }
    return 0;
  }


  void onRead(bufferevent *evt, void *args) {
    Peer *peer = (Peer *)args;

    switch (peer->state) {
    case PS_ACCEPTED: {
      Handshake hdsk;
      std::size_t size = bufferevent_read(evt, &hdsk, sizeof(Handshake));
      if (size < sizeof(Handshake)) {
	onError(evt, 0, peer);
	return;
      }

      bufferevent_setwatermark(evt, EV_READ, sizeof(Header), READBUF_SIZE);
      registerPeer(makeIdent(peer->addr.sin_addr.s_addr, hdsk), peer);
      int res = bufferevent_enable(evt, EV_READ);
      if (res != 0) {
	onError(evt, 0, peer);
	return;
      }
      peer->state = PS_HEADER;
      break;
    }

    case PS_HEADER: {
      Header hdr;
      std::size_t size = bufferevent_read(evt, &hdr, sizeof(Header));
      if (size < sizeof(Header)) {
	onError(evt, 0, peer);
	return;
      }

      bufferevent_setwatermark(evt, EV_READ, hdr, READBUF_SIZE);
      int res = bufferevent_enable(evt, EV_READ);
      if (res != 0) {
	onError(evt, 0, peer);
	return;
      }

      peer->state = PS_BODY;
      break;
    }

    case PS_BODY: {
      Header hdr;
      std::size_t size = bufferevent_read(evt, &hdr, sizeof(Header));
      if (size < sizeof(Header)) {
	onError(evt, 0, peer);
	return;
      }

      bufferevent_setwatermark(evt, EV_READ, hdr, READBUF_SIZE);
      int res = bufferevent_enable(evt, EV_READ);
      if (res != 0) {
	onError(evt, 0, peer);
	return;
      }

      peer->state = PS_BODY;
      break;
    }

    case PS_CONNECTING:
    }
  }


  void onWrite(bufferevent *evt, void *args) {
    
  }

  void onError(bufferevent *evt, const short what, void *args) {

  }

  void onAccept(const int fd, const short evt, void *args) {
    socklen_t socklen;
    int peerFd = accept(fd, (sockaddr_in *)&peer->addr, &socklen);
    if (peerFd < 0) {
      goto fail;
    }

    try {
      Peer *peer = new Peer;
    } catch (Error &e) {
      goto fd;
    }
    if (!peer) {
      goto fd;
    }

    peer->state = PS_ACCEPTED;
    bufferevent_setfd(peer->evt, peerFd);
    bufferevent_setwatermark(peer->evt, EV_READ, sizeof(Handshake), READBUF_SIZE);
    int res = bufferevent_enable(peer->evt, EV_READ);
    if (res != 0) {
      goto peer;
    }

    return;
  peer:
    delete peer;
  fd:
    close(peerFd);
  fail:
  }


  void init(const unsigned short port, const int backlog = 10) {
    event_base *base = event_init();
    if (!base) {
      throw Error("event_init");
    }

    listenAddr.sin_family = AF_INET;
    listenAaddr.sin_addr.s_addr = INADDR_ANY;
    listenAddr.sin_port = htons(port);

    listenFd = socket(AF_INET, SOCk_STREAM, IPPROTO_TCP);
    if (listenFd < 0) {
      throw Error("socket");
    }

    int res = bind(listenFd, (sockaddr *)&listenAddr, sizeof(sockaddr_in));
    if (res < 0) {
      throw Error("bind");
    }

    res = listen(listenFd, backlog);
    if (res < 0) {
      throw Error("listen");
    }

    res = setNonblock(listenFd);
    if (res < 0) {
      throw Error("setNonblock");
    }

    res = event_set(&listenEvt, listenFd, EV_READ | EV_PERSIST, onAccept, NULL);
    if (res != 0) {
      throw Error("event_set");
    }
    res = event_add(&listenEvt, NULL);
    if (res != 0) {
      throw Error("event_add");
    }
  }


  void fini() {
    close(listenFd);
  }

  void hook(char *node, MesonHook hook) {
  }

  void send(void *data,
	    size_t len,
	    char *node) {
  }

  void timer() {
  }

  void run() {
    event_dispatch();
  }
}
